/**
 * Copyright (C) 2010-2016 eBusiness Information, Excilys Group
 * Copyright (C) 2016-2020 the AndroidAnnotations project
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed To in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.ohosannotations.internal.core.handler;

import com.helger.jcodemodel.AbstractJClass;
import com.helger.jcodemodel.IJExpression;
import com.helger.jcodemodel.JBlock;
import com.helger.jcodemodel.JExpr;
import com.helger.jcodemodel.JFieldRef;
import com.helger.jcodemodel.JInvocation;
import com.helger.jcodemodel.JMethod;
import com.helger.jcodemodel.JVar;

import org.ohosannotations.ElementValidation;
import org.ohosannotations.OhosAnnotationsEnvironment;
import org.ohosannotations.annotations.ResId;
import org.ohosannotations.annotations.sharedpreferences.DefaultBoolean;
import org.ohosannotations.annotations.sharedpreferences.DefaultFloat;
import org.ohosannotations.annotations.sharedpreferences.DefaultInt;
import org.ohosannotations.annotations.sharedpreferences.DefaultLong;
import org.ohosannotations.annotations.sharedpreferences.DefaultRes;
import org.ohosannotations.annotations.sharedpreferences.DefaultString;
import org.ohosannotations.annotations.sharedpreferences.DefaultStringSet;
import org.ohosannotations.annotations.sharedpreferences.SharedPref;
import org.ohosannotations.api.sharedpreferences.AbstractPrefField;
import org.ohosannotations.api.sharedpreferences.BooleanPrefField;
import org.ohosannotations.api.sharedpreferences.FloatPrefField;
import org.ohosannotations.api.sharedpreferences.IntPrefField;
import org.ohosannotations.api.sharedpreferences.LongPrefField;
import org.ohosannotations.api.sharedpreferences.StringPrefField;
import org.ohosannotations.api.sharedpreferences.StringSetPrefField;
import org.ohosannotations.helper.CanonicalNameConstants;
import org.ohosannotations.helper.IdAnnotationHelper;
import org.ohosannotations.helper.IdValidatorHelper;
import org.ohosannotations.helper.IdValidatorHelper.FallbackStrategy;
import org.ohosannotations.holder.SharedPrefHolder;
import org.ohosannotations.rclass.IRClass;
import org.ohosannotations.rclass.IRClass.Res;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.ElementFilter;

import static com.helger.jcodemodel.JExpr.invoke;
import static com.helger.jcodemodel.JExpr.lit;
import static com.helger.jcodemodel.JMod.PRIVATE;
import static com.helger.jcodemodel.JMod.STATIC;

/**
 * 共享”处理程序
 *
 * @author dev
 * @date 2021/07/26
 * @since 2021-07-23
 */
public class SharedPrefHandler extends CoreBaseGeneratingAnnotationHandler<SharedPrefHolder> {
    /**
     * 默认的参照信息
     * DefaultPrefInfo
     *
     * @param <T>
     * @author dev
     * @since 2021-07-23
     */
    private static final class DefaultPrefInfo<T> {
        final Class<? extends Annotation> annotationClass;
        final Class<? extends AbstractPrefField<?>> prefFieldClass;
        final IRClass.Res resType;
        final T defaultValue;
        final String fieldHelperMethodName;

        DefaultPrefInfo(Class<? extends Annotation> annotationClass,
            Class<? extends AbstractPrefField<?>> prefFieldClass, Res resType, T defaultValue, String fieldHelperMethodName) {
            this.annotationClass = annotationClass;
            this.prefFieldClass = prefFieldClass;
            this.resType = resType;
            this.defaultValue = defaultValue;
            this.fieldHelperMethodName = fieldHelperMethodName;
        }
    }

    /**
     * 默认的参照信息
     */
    private static final Map<String, DefaultPrefInfo<?>> DEFAULT_PREF_INFOS
        = new HashMap<String, DefaultPrefInfo<?>>() {
        private static final long serialVersionUID = 1L;

        {
            put("boolean", new DefaultPrefInfo<>(DefaultBoolean.class,
                BooleanPrefField.class, Res.BOOLEAN, false, "booleanField"));
            put("float", new DefaultPrefInfo<>(DefaultFloat.class, FloatPrefField.class,
                Res.FLOAT, 0f, "floatField"));
            put("int", new DefaultPrefInfo<>(DefaultInt.class, IntPrefField.class,
                IRClass.Res.INTEGER, 0, "intField"));
            put("long", new DefaultPrefInfo<>(DefaultLong.class, LongPrefField.class,
                IRClass.Res.INTEGER, 0L, "longField"));
            put(CanonicalNameConstants.STRING, new DefaultPrefInfo<>(DefaultString.class, StringPrefField.class,
                IRClass.Res.STRING, "", "stringField"));
            put(CanonicalNameConstants.STRING_SET, new DefaultPrefInfo<Set<String>>(DefaultStringSet.class,
                StringSetPrefField.class, IRClass.Res.STRARRAY, null, "stringSetField"));
        }
    };

    /**
     * 共享”处理程序
     * SharedPrefHandler
     *
     * @param environment environment
     */
    public SharedPrefHandler(OhosAnnotationsEnvironment environment) {
        super(SharedPref.class, environment);
    }

    /**
     * 创建生成类持有人
     * createGeneratedClassHolder
     *
     * @param environment 环境
     * @param annotatedElement 带注释的元素
     * @return SharedPrefHolder
     * @throws Exception 异常
     */
    @Override
    public SharedPrefHolder createGeneratedClassHolder(OhosAnnotationsEnvironment environment,
        TypeElement annotatedElement) throws Exception {
        return new SharedPrefHolder(environment, annotatedElement);
    }

    /**
     * 验证
     * SharedPrefHolder
     *
     * @param element 元素
     * @param validation 验证
     */
    @Override
    public void validate(Element element, ElementValidation validation) {
        super.validate(element, validation);

        TypeElement typeElement = (TypeElement) element;

        validatorHelper.isInterface(typeElement, validation);

        List<? extends Element> inheritedMembers = getProcessingEnvironment()
            .getElementUtils().getAllMembers(typeElement);

        for (Element memberElement : inheritedMembers) {
            if (!memberElement.getEnclosingElement().asType()
                .toString().equals("java.lang.Object")) {
                coreValidatorHelper.isPrefMethod(memberElement, validation);

                DefaultPrefInfo<?> info;
                IdValidatorHelper defaultAnnotationValidatorHelper = null;

                if (validation.isValid()) {
                    info = DEFAULT_PREF_INFOS.get(((ExecutableElement) memberElement).getReturnType().toString());
                    coreValidatorHelper.hasCorrectDefaultAnnotation((ExecutableElement) memberElement, validation);

                    if (validation.isValid() && memberElement.getAnnotation(DefaultRes.class) != null) {
                        defaultAnnotationValidatorHelper = new IdValidatorHelper
                            (new IdAnnotationHelper(getEnvironment(), DefaultRes.class.getName()));
                        defaultAnnotationValidatorHelper.resIdsExist(memberElement,
                            info.resType, FallbackStrategy.USE_ELEMENT_NAME, validation);
                    } else if (validation.isValid() && memberElement.getAnnotation
                        (info.annotationClass) != null) {
                        defaultAnnotationValidatorHelper = new IdValidatorHelper
                            (new IdAnnotationHelper(getEnvironment(), info.annotationClass.getName()));
                    } else {
                    }

                    if (validation.isValid() && defaultAnnotationValidatorHelper != null) {
                        defaultAnnotationValidatorHelper
                            .annotationParameterIsOptionalValidResId(memberElement,
                                IRClass.Res.STRING, "keyRes", validation);
                    }
                }
            }
        }

        SharedPref sharedPrefAnnotation = element.getAnnotation(SharedPref.class);
        SharedPref.Scope scope = sharedPrefAnnotation.value();
        String name = sharedPrefAnnotation.name();
        boolean hasCustomName = !name.trim().isEmpty();
        EnumSet<SharedPref.Scope> allowedScopes = EnumSet
            .of(SharedPref.Scope.ABILITY, SharedPref.Scope.UNIQUE);
        if (hasCustomName && !allowedScopes.contains(scope)) {
            validation.addError("SharedPref#name() is only supported for Scope.Ability and Scope.UNIQUE.");
        }
    }

    /**
     * 过程
     * process
     *
     * @param element 元素
     * @param holder 持有人
     */
    @Override
    public void process(Element element, SharedPrefHolder holder) {
        generateConstructor(element, holder);
        generateFieldMethodAndEditorFieldMethod(element, holder);
    }

    /**
     * 生成的构造函数
     * generateConstructor
     *
     * @param element 元素
     * @param holder 持有人
     */
    private void generateConstructor(Element element, SharedPrefHolder holder) {
        SharedPref sharedPrefAnnotation = element.getAnnotation(SharedPref.class);
        SharedPref.Scope scope = sharedPrefAnnotation.value();
        String name = sharedPrefAnnotation.name();

        if (name.trim().isEmpty()) {
            name = element.getSimpleName().toString();
        }
        JBlock constructorSuperBlock = holder.getConstructor().body();
        JVar contextParam = holder.getConstructorContextParam();

        /**
         * 生成如下代码, mode无效
         *     public UniquePrefs_(Context context) {
         *         DatabaseHelper databaseHelper = new DatabaseHelper(context);
         *         super(databaseHelper.getPreferences("UniquePrefs"));
         *     }
         */

        // 1.生成 DatabaseHelper databaseHelper = new DatabaseHelper(context);
        JInvocation databaseHelper = JExpr._new(getClasses().DATABASEHELPER).arg(contextParam);
        // 2.生成 super(databaseHelper.getPreferences(fileName))
        switch (scope) {
            case ABILITY_DEFAULT: {
                JMethod getLocalClassName = getLocalClassName(holder);
                constructorSuperBlock.invoke("super")
                    .arg(databaseHelper.invoke("getPreferences")
                        .arg(invoke(getLocalClassName).arg(contextParam)));
                break;
            }
            case ABILITY: {
                JMethod getLocalClassName = getLocalClassName(holder);
                constructorSuperBlock.invoke("super")
                    .arg(databaseHelper.invoke("getPreferences")
                        .arg(invoke(getLocalClassName).arg(contextParam)
                            .plus(lit("_" + name))));
                break;
            }
            case UNIQUE: {
                constructorSuperBlock.invoke("super")
                    .arg(databaseHelper.invoke("getPreferences")
                        .arg(lit(name)));
                break;
            }
            case APPLICATION_DEFAULT: {
                constructorSuperBlock.invoke("super")
                    .arg(databaseHelper.invoke("getPreferences")
                        .arg("default"));
                break;
            }
        }
    }

    /**
     * 得到当地的类名
     * getLocalClassName
     *
     * @param holder 持有人
     * @return getLocalClassName
     */
    private JMethod getLocalClassName(SharedPrefHolder holder) {
        AbstractJClass stringClass = getClasses().STRING;
        JMethod getLocalClassName = holder.getGeneratedClass()
            .method(PRIVATE | STATIC, stringClass, "getLocalClassName");
        AbstractJClass contextClass = getClasses().CONTEXT;

        JVar contextParam = getLocalClassName.param(contextClass, "context");

        JBlock body = getLocalClassName.body();

        JVar packageName = body.decl(stringClass, "packageName", contextParam.invoke("getBundleName"));

        JVar className = body.decl(stringClass, "className", contextParam.invoke("getLocalClassName"));

        JVar packageLen = body.decl(getCodeModel().INT, "packageLen", packageName.invoke("length"));

        IJExpression condition = className.invoke("startsWith").arg(packageName).not() //
            .cor(className.invoke("length").lte(packageLen)) //
            .cor(className.invoke("charAt").arg(packageLen).ne(lit('.')));

        body._if(condition)._then()._return(className);

        body._return(className.invoke("substring").arg(packageLen.plus(lit(1))));

        return getLocalClassName;
    }

    /**
     * 生成方法和编辑字段的方法
     * generateFieldMethodAndEditorFieldMethod
     *
     * @param element 元素
     * @param sharedPrefHolder 共享”持有人
     */
    private void generateFieldMethodAndEditorFieldMethod(Element element,
        SharedPrefHolder sharedPrefHolder) {
        for (ExecutableElement method : getValidMethods(element)) {
            IJExpression keyExpression = generateFieldMethod(sharedPrefHolder, method);
            sharedPrefHolder.createEditorFieldMethods(method, keyExpression);
        }
    }

    /**
     * 得到有效的方法
     * getValidMethods
     *
     * @param element 元素
     * @return validMethods
     */
    private List<ExecutableElement> getValidMethods(Element element) {
        List<? extends Element> members = element.getEnclosedElements();
        List<ExecutableElement> methods = ElementFilter.methodsIn(members);
        List<ExecutableElement> validMethods = new ArrayList<>();
        for (ExecutableElement method : methods) {
            validMethods.add(method);
        }
        return validMethods;
    }

    /**
     * 生成场法
     * generateFieldMethod
     *
     * @param holder 持有人
     * @param method 方法
     * @return createFieldMethod
     */
    private IJExpression generateFieldMethod(SharedPrefHolder holder, ExecutableElement method) {
        DefaultPrefInfo<?> info = DEFAULT_PREF_INFOS.get(method.getReturnType().toString());
        return createFieldMethod(holder, method, info.annotationClass,
            info.prefFieldClass, info.defaultValue, info.resType, info.fieldHelperMethodName);
    }

    /**
     * 创建领域的方法
     * createFieldMethod
     *
     * @param holder 持有人
     * @param method 方法
     * @param annotationClass 注释类
     * @param prefFieldClass 参照领域类
     * @param defaultValue 默认值
     * @param resType res类型
     * @param fieldHelperMethodName 场辅助方法名称
     * @return keyExpression
     */
    private IJExpression createFieldMethod(SharedPrefHolder holder,
        ExecutableElement method, Class<? extends Annotation> annotationClass,
        Class<? extends AbstractPrefField<?>> prefFieldClass, Object defaultValue, Res resType, String fieldHelperMethodName) {
        Annotation annotation = method.getAnnotation(annotationClass);
        IJExpression defaultValueExpr;

        Object value = null;
        if (annotation != null) {
            value = annotationHelper.extractAnnotationParameter(method,
                annotationClass.getName(), "value");
        }

        if (annotation != null && method.getAnnotation(DefaultStringSet.class) == null) {
            defaultValueExpr = codeModelHelper.litObject(value);
        } else if (method.getAnnotation(DefaultRes.class) != null) {
            defaultValueExpr = extractResValue(holder, method, resType);
            annotationClass = DefaultRes.class;
        } else if (method.getAnnotation(DefaultStringSet.class) != null) {
            if (value != null) {
                Set<String> arrayValues = new HashSet<>(Arrays.asList((String[]) value));
                value = arrayValues;

                if (arrayValues.isEmpty()) {
                    defaultValueExpr = newEmptyStringHashSet();
                } else {
                    JInvocation arrayAsList = getClasses().ARRAYS.staticInvoke("asList");
                    for (String arrayValue : arrayValues) {
                        arrayAsList.arg(lit(arrayValue));
                    }
                    defaultValueExpr = JExpr._new(getClasses().HASH_SET.narrow(getClasses().STRING)).arg(arrayAsList);
                }
            } else {
                defaultValueExpr = newEmptyStringHashSet();
            }
            annotationClass = DefaultStringSet.class;
        } else {
            defaultValueExpr = defaultValue != null ? codeModelHelper.litObject(defaultValue) : newEmptyStringHashSet();
            annotationClass = null;
        }

        Integer keyResId = ResId.DEFAULT_VALUE;

        if (annotationClass != null) {
            keyResId = annotationHelper
                .extractAnnotationParameter(method, annotationClass.getName(), "keyRes");
        }

        IJExpression keyExpression = getIjExpression(holder, method,
            prefFieldClass, defaultValue, fieldHelperMethodName, defaultValueExpr, value, keyResId);
        return keyExpression;
    }


    private IJExpression getIjExpression(SharedPrefHolder holder, ExecutableElement method,
        Class<? extends AbstractPrefField<?>> prefFieldClass, Object defaultValue,
        String fieldHelperMethodName, IJExpression defaultValueExpr, Object value, Integer keyResId) {
        IJExpression keyExpression;
        String fieldName = method.getSimpleName().toString();

        if (keyResId == ResId.DEFAULT_VALUE) {
            keyExpression = lit(fieldName);
        } else {
            IRClass rClass = getEnvironment().getRClass();
            JFieldRef idRef = rClass.getIdStaticRef(keyResId, getEnvironment());
            keyExpression = holder.getEditorContextField().invoke("getString").arg(idRef);
        }

        String docComment = getProcessingEnvironment().getElementUtils().getDocComment(method);
        String defaultValueStr = value == null ? null : value.toString();
        if (defaultValueStr == null) {
            defaultValueStr = defaultValue == null ? null : defaultValue.toString();
        }
        holder.createFieldMethod(prefFieldClass, keyExpression, fieldName,
            fieldHelperMethodName, defaultValueExpr, docComment, defaultValueStr);
        return keyExpression;
    }

    /**
     * 提取res价值
     * keyExpression
     *
     * @param holder 持有人
     * @param method 方法
     * @param res res
     * @return resourceInvocation
     */
    private IJExpression extractResValue(SharedPrefHolder holder,
        Element method, IRClass.Res res) {
        JFieldRef idRef = annotationHelper
            .extractOneAnnotationFieldRef(method, DefaultRes
                .class.getCanonicalName(), res, true);

        String resourceGetMethodName = null;
        switch (res) {
            case BOOLEAN:
                resourceGetMethodName = "getBoolean";
                break;
            case INTEGER:
                resourceGetMethodName = "getInteger";
                break;
            case FLOAT:
                resourceGetMethodName = "getFloat";
                break;
            case STRING:
                resourceGetMethodName = "getString";
                break;
            case STRARRAY:
                resourceGetMethodName = "getStringArray";
                break;
            default:
                break;
        }

        JInvocation resourceInvocation = holder.getContextField().invoke(resourceGetMethodName).arg(idRef);

        if (Res.STRING.equals(res)) {
            // 生成 context.getString()
            resourceInvocation = holder.getContextField().invoke("getString").arg(idRef);
        } else if (Res.STRARRAY.equals(res)) {
            // 1.生成context.getStringArray()
            resourceInvocation = holder.getContextField().invoke("getStringArray").arg(idRef);
            JInvocation asList = getClasses().ARRAYS.staticInvoke("asList");
            JInvocation newHashMap = JExpr._new(getClasses().HASH_SET.narrow(getClasses().STRING));
            resourceInvocation = newHashMap.arg(asList.arg(resourceInvocation));
        } else if (Res.INTEGER.equals(res)) {
            resourceInvocation = JExpr.invoke("getInteger").arg(holder.getContextField()).arg(idRef);
        } else if (Res.FLOAT.equals(res)) {
            resourceInvocation = JExpr.invoke("getFloat").arg(holder.getContextField()).arg(idRef);
        } else if (Res.BOOLEAN.equals(res)) {
            resourceInvocation = JExpr.invoke("getBoolean").arg(holder.getContextField()).arg(idRef);
        } else {
        }
        return resourceInvocation;
    }

    /**
     * 新空字符串哈希
     *
     * @return {@link IJExpression}
     */
    private IJExpression newEmptyStringHashSet() {
        return JExpr._new(getClasses().HASH_SET.narrow(getClasses().STRING)).arg(lit(0));
    }
}
